package com.cnn.base.util;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Supporting content replication between different objects or the same object
 * 
 * @author liuzeke
 * @version 1.0
 */
public abstract class BeanUtil {

	/**
	 * Can not be inherited
	 */
	private BeanUtil() {
	}

	/**
	 * S -> T
	 * 
	 * @author liuzeke
	 * @param src
	 *            Source object
	 * @param tar
	 *            Target object
	 * @param strs
	 *            Ignored attributes
	 * @return Target object
	 */
	@SuppressWarnings({ "unchecked", "null" })
	public static <S, T> T copyProperty(final S src, T tar, String... strs) {

		if (src == null)
			return tar;

		try {
			tar = (tar == null) ? (T) tar.getClass().newInstance() : tar;
		} catch (InstantiationException | IllegalAccessException e) {
			throw new RuntimeException(e);
		}

		Class<?> clazzSrc = src.getClass();
		Class<?> clazzTar = tar.getClass();

		CacheMethods cacheGet = null;
		CacheMethods cacheSet = null;
		try {
			cacheGet = getMethods(clazzSrc, (byte) 0);
			cacheSet = getMethods(clazzTar, (byte) 1);
		} catch (NoSuchFieldException | SecurityException e1) {
			throw new RuntimeException(e1);
		}

		for (Map.Entry<String, Method> e : cacheSet.sets.entrySet()) {
			try {
				String fieldName = clazzSrc.getDeclaredField(e.getKey()).getName();
				if (isIgnore(fieldName, strs))
					continue;
				Object v = cacheGet.gets.get(fieldName).invoke(src);
				if (v == null || v.toString().trim().length() == 0)
					continue;
				e.getValue().invoke(tar, v);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}
		return tar;
	}

	/**
	 * S -> T
	 * 
	 * @author liuzeke
	 * @param src
	 *            Source object
	 * @param clazz
	 *            Target object
	 * @param strs
	 *            Ignored attributes
	 * @return Target object
	 */
	public static <S, T> T copyProperty(final S src, Class<T> clazz, String... strs) {

		try {
			return copyProperty(src, clazz.newInstance(), strs);
		} catch (InstantiationException | IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @author liuzeke
	 * @param clazz
	 *            Source object or target object
	 * @param flag
	 *            Flag equals 0, returns the <code>get</code> method, flag
	 *            equals 1, returns the <code>set</code> method
	 * @return CacheMethods is used to cache <code>get</code> and
	 *         <code>set</code> methods
	 * @throws NoSuchFieldException
	 * @throws SecurityException
	 */
	private static CacheMethods getMethods(Class<?> clazz, byte flag) throws NoSuchFieldException, SecurityException {
		final CacheMethods cm = new CacheMethods();
		for (Method m : clazz.getMethods()) {
			String mName = m.getName();
			if (isGetOrIs(mName) && m.getParameterTypes().length == 0 && flag == 0)
				cm.gets.put(getFieldName(mName), m);
			if (isSet(mName) && m.getParameterTypes().length == 1 && flag == 1)
				cm.sets.put(getFieldName(mName), m);
		}
		cm.gets.remove("class");
		return cm;
	}

	/**
	 * @author liuzeke
	 * @version 1.0
	 */
	static class CacheMethods {
		final Map<String, Method> gets = new HashMap<String, Method>();
		final Map<String, Method> sets = new HashMap<String, Method>();
	}

	private static boolean isGetOrIs(String mName) {
		return Pattern.compile("^get\\w*|is\\w*$").matcher(mName).matches();
	}

	private static boolean isSet(String mName) {
		return Pattern.compile("^set\\w*$").matcher(mName).matches();
	}

	private static boolean isIs(String mName) {
		return Pattern.compile("^is\\w*$").matcher(mName).matches();
	}

	private static String getFieldName(String methodName) {
		return isIs(methodName) ? methodName.substring(2, 3).toLowerCase() + methodName.substring(3)
				: methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
	}

	private static boolean isIgnore(final String src, final String[] dest) {
		if (src == null | src.trim().length() == 0 || dest == null || dest.length == 0)
			return false;
		for (String s : dest)
			if (src.equals(s))
				return true;
		return false;
	}
}
